/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zh.rxharmony.schedulers;

import io.reactivex.Scheduler;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.Disposables;
import io.reactivex.plugins.RxJavaPlugins;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Harmony主线程调度器，内部通过EventHandler，发送消息到主线程，对要执行的异步任务进行处理，实现线程切换
 */
public class EventHandlerScheduler extends Scheduler {
    private final WithIdEventHandler eventHandler;

    /**
     * 构造方法，保存传入的EventHandler实例
     */
    EventHandlerScheduler(WithIdEventHandler eventHandler) {
        this.eventHandler = eventHandler;
    }

    /**
     * 调度方法
     *
     * @param run   要执行的异步任务
     * @param delay 延时时间
     * @param unit  时间单位
     * @return 返回Disposable实例，被对任务进行取消
     */
    @NonNull
    @Override
    public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) {
        //判空
        if (run == null) throw new NullPointerException("run == null");
        if (unit == null) throw new NullPointerException("unit == null");

        //执行hook回调函数，如果没有设置，会返回传入的Runnable实例
        run = RxJavaPlugins.onSchedule(run);

        //对异步任务进行包装，ScheduledRunnable实现了Runnable和Disposable接口，可以对异步任务进行取消
        ScheduledRunnable scheduled = new ScheduledRunnable(eventHandler, run);

        //获取一个InnerEvent消息
        InnerEvent innerEvent = InnerEvent.get(scheduled);
        //发送消息到主线程
        eventHandler.sendEvent(innerEvent, unit.toMillis(delay));
        //返回任务，外部可以对该任务进行取消
        return scheduled;
    }

    /**
     * 创建Worker实例，会创建HandlerWorker实例并返回
     */
    @NonNull
    @Override
    public Worker createWorker() {
        return new EventHandlerWorker(eventHandler);
    }

    /**
     * Worker子类
     */
    private static final class EventHandlerWorker extends Worker {
        private final WithIdEventHandler handler;

        /**
         * 是否被切断标志位
         */
        private volatile boolean disposed;

        /**
         * 参数生成器，用于在 dispose() 方法中，移除该Worker调度的任务
         */
        private static final AtomicLong paramsCreator = new AtomicLong();
        /**
         * 参数
         */
        private final long params;

        EventHandlerWorker(WithIdEventHandler eventHandler) {
            this.handler = eventHandler;
            params = paramsCreator.incrementAndGet();
        }

        @Override
        public Disposable schedule(Runnable run, long delay, TimeUnit unit) {
            //判空
            if (run == null) throw new NullPointerException("run == null");
            if (unit == null) throw new NullPointerException("unit == null");

            //如果被切断，直接返回
            if (disposed) {
                return Disposables.disposed();
            }

            //执行hook函数，如果没有设置，则会返回传入的Runnable对象
            run = RxJavaPlugins.onSchedule(run);

            //对异步任务进行包装
            ScheduledRunnable scheduled = new ScheduledRunnable(handler, run);

            //获取一个InnerEvent消息
            InnerEvent innerEvent = InnerEvent.get(scheduled);
            //保存异步任务到事件中，在EventHandler的processEvent()回调时，进行处理
            innerEvent.object = this;
            //保存Worker的参数到消息，在dispose()中，移除消息
            innerEvent.param = params;

            //发送消息到主线程进行执行
            handler.sendEvent(innerEvent, unit.toMillis(delay));

            //再次检查是否被切断，如果被切断，则取消任务，直接返回
            if (disposed) {
                handler.removeTask(scheduled);
                return Disposables.disposed();
            }
            return scheduled;
        }

        @Override
        public void dispose() {
            //被切断了，记录标志位，移除任务
            disposed = true;
            //通过 param 参数移除该 Worker 调度，单未执行的 InnerEvent
            handler.removeEvent(handler.id, params);
        }

        @Override
        public boolean isDisposed() {
            //返回切断标志位
            return disposed;
        }
    }

    /**
     * 对异步任务进行包装，实现Disposable接口，提供可取消任务的功能
     */
    private static final class ScheduledRunnable implements Runnable, Disposable {
        private final EventHandler handler;
        private final Runnable delegate;

        /**
         * 切断标志
         */
        private volatile boolean disposed;

        ScheduledRunnable(EventHandler handler, Runnable delegate) {
            this.handler = handler;
            this.delegate = delegate;
        }

        @Override
        public void run() {
            try {
                //执行异步任务
                delegate.run();
            } catch (Throwable t) {
                //如果出现异常，交给hook回调函数处理
                RxJavaPlugins.onError(t);
            }
        }

        @Override
        public void dispose() {
            //被切断，移除任务
            handler.removeTask(this);
            disposed = true;
        }

        @Override
        public boolean isDisposed() {
            //返回是否被切断
            return disposed;
        }
    }

    /**
     * EventHandler 带一个消息Id
     */
    static class WithIdEventHandler extends EventHandler {
        private static final AtomicInteger idCreator = new AtomicInteger();
        /**
         * 每个EventHandler都配置一个id，通过这个EventHandler发送的事件，它的eventId都统一为这个id
         */
        private final int id;

        public WithIdEventHandler(EventRunner runner) throws IllegalArgumentException {
            super(runner);
            //生成Id
            id = idCreator.incrementAndGet();
        }

        @Override
        protected void processEvent(InnerEvent event) {
            super.processEvent(event);
            //非当前Handler的事件不处理
            if (event.eventId != id) {
                return;
            }
            Object obj = event.object;
            if (obj instanceof Runnable) {
                ((Runnable) obj).run();
            }
        }
    }
}